package bsh;

import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;

import bsh.exception.CompilationAssertionFailed;
import bsh.exception.CompilationFailedException;
import bsh.meta.JavaClassWrapper;
import bsh.meta.Scope;
import bsh.meta.ScopeType;
import bsh.meta.TypeWrapper;

/**
 * Compiler para loops while
 * 
 * @author takeshi
 */
public class WhileStatementCompiler implements NodeCompiler {

    private final BSHWhileStatement node;

    private final CompilationUnit unit;

    /**
     * @param node node
     * @param unit unti
     */
    public WhileStatementCompiler(BSHWhileStatement node, CompilationUnit unit) {
        super();
        this.node = node;
        this.unit = unit;
    }

    /**
     * {@inheritDoc}
     */
    public void compile(MethodVisitor host, CompilationState state) throws CompilationFailedException {
        state.addLineNumber(host, this.node.getLineNumber());
        Scope loopScope = state.newScope(ScopeType.LOOP_BLOCK);
        Scope body = state.newScope(ScopeType.BLOCK);
        if (this.node.isDoStatement) {
            // do it once before the condition is evaluated
            host.visitJumpInsn(Opcodes.GOTO, body.getStartLabel());
            loopScope.installStartLabel(host, state.getUnit().options());
            this.compileDoStatement(host, state, loopScope, body);
        } else {
            loopScope.installStartLabel(host, state.getUnit().options());
            this.compileWhileStatement(host, state, loopScope, body);
        }
        state.popScope();
        loopScope.installEndLabel(host, this.unit.options());
        Scope popScope = state.popScope();
        if (popScope != loopScope) {
            throw new CompilationAssertionFailed("wrong scope on top!");
        }
    }

    /**
     * @param host host
     * @param state state
     * @throws CompilationFailedException e
     */
    private void compileBody(MethodVisitor host, CompilationState state) throws CompilationFailedException {
        SimpleNode bodyNode = this.node.getChild(1);
        this.unit.getNodeCompiler(bodyNode).compile(host, state);
        BlockNodeCompiler.checkStackEmpty(host, state);
    }

    /**
     * Compila a condicao do loop.
     * 
     * @param host host
     * @param state state
     * @param scope scope
     * @param condition noh que contem a condicao
     * @param body o corpo
     * @throws CompilationFailedException e
     */
    private void compileCondition(MethodVisitor host, CompilationState state, Scope scope, final SimpleNode condition, Scope body)
            throws CompilationFailedException {
        this.unit.getNodeCompiler(condition).compile(host, state);
        state.assertMinimumStackSize(1);
        int old = state.getStackSize();
        this.unit.getCompilerManager().coerceStackTopTo(host, JavaClassWrapper.BOOLEAN_WRAPPER, state);
        state.assertStackTopType(JavaClassWrapper.BOOLEAN_WRAPPER);
        state.assertStackSize(old);
        state.popStack();
        host.visitJumpInsn(Opcodes.IFEQ, scope.getEndLabel());
        host.visitJumpInsn(Opcodes.GOTO, body.getStartLabel());
    }

    /**
     * Compila um while statement
     * 
     * @param host o host
     * @param state state
     * @param scope scopo
     * @param body o escopo do corpo do while
     * @throws CompilationFailedException e
     */
    private void compileWhileStatement(MethodVisitor host, CompilationState state, Scope scope, Scope body)
            throws CompilationFailedException {
        final SimpleNode condition = this.node.getChild(0);
        this.compileCondition(host, state, scope, condition, body);
        body.installStartLabel(host, state.getUnit().options());
        if (this.node.jjtGetNumChildren() > 1) {
            this.compileBody(host, state);
        }
        body.installEndLabel(host, unit.options());
        host.visitJumpInsn(Opcodes.GOTO, scope.getStartLabel());
    }

    /**
     * Compila um bloco do { .... } while
     * 
     * @param host host
     * @param state state
     * @param scope escopo atual
     * @param body escopo do corpo
     * @throws CompilationFailedException e
     */
    private void compileDoStatement(MethodVisitor host, CompilationState state, Scope scope, Scope body) throws CompilationFailedException {
        final boolean hasBody = this.node.jjtGetNumChildren() > 1;
        SimpleNode condition = this.node.getChild(hasBody ? 1 : 0);
        this.compileCondition(host, state, scope, condition, body);
        body.installStartLabel(host, state.getUnit().options());
        if (hasBody) {
            SimpleNode bodyNode = this.node.getChild(0);
            this.unit.getNodeCompiler(bodyNode).compile(host, state);
        }
        body.installEndLabel(host, unit.options());
        host.visitJumpInsn(Opcodes.GOTO, scope.getStartLabel());
    }

    /**
     * {@inheritDoc}
     */
    public TypeWrapper nodeResultType(Scope scope) throws CompilationFailedException {
        return JavaClassWrapper.VOID_WRAPPER;
    }

}
